<?php
namespace Core\Common\Helper;

use Core\Common\Base\Redis;

/**
 * 限制器，限制某个时间段某个行为只能做几次
 * Class RedisLimiter
 * @package App\Helper
 */
class RedisLimiter
{
    /**
     * @var Redis|\Redis Redis实例
     */
    protected $redis;

    /**
     * @var string 限制器名称
     */
    protected $key;

    /**
     * @var int 限制上限计数
     */
    protected $maximum;

    /**
     * @var int 具体在哪个时间点过期
     */
    protected $expireTime;


    public function __construct($redis, $key, $maximum, $expireTime)
    {
        $this->redis = $redis;
        $this->key = $key;
        $this->maximum = $maximum;
        $this->expireTime = $expireTime;
    }

    /**
     * 当前值
     * @return int
     */
    public function cur()
    {
        return (int)$this->redis->get($this->key);
    }

    /**
     * 剩余次数
     * @return int
     */
    public function remain()
    {
        $result = $this->maximum - $this->cur();
        if ($result < 0) {
            return 0;
        }
        return $result;
    }

    /**
     * 增加次数
     * @param int $i
     * @return int
     */
    public function incr($i = 1)
    {
        $ret = $this->redis->incrBy($this->key, $i);
        if ($ret == $i) {
            $this->redis->expireAt($this->key, $this->expireTime);
        }
        return $ret;
    }

    /**
     * 如果还没到达上限，执行函数(函数执行失败时需要返回false)
     * @param callable $func
     * @param int $delta
     * @return bool
     */
    public function ifAllow($func, $delta = 1)
    {
        if ($this->acquire($delta)) {
            $ret = call_user_func($func);
            if ($ret === false) {
                $this->rollback($delta);
                return false;
            }
            return $ret;
        }
        return false;
    }

    /**
     * 申请
     * @param int $delta
     * @return bool|int
     */
    public function acquire($delta = 1)
    {
        if (($this->cur()+$delta > $this->maximum)) {
            return false;
        }
        $ret = $this->incr($delta);
        if ($ret > $this->maximum) {
            $this->rollback($delta);
            return false;
        }
        return $ret;
    }

    /**
     * 执行失败时归还
     * @param int $delta
     * @return bool
     */
    public function rollback($delta = 1)
    {
        $ret = $this->redis->incrBy($this->key, -$delta);
        return ($ret !== false);
    }
}